﻿#region Import
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using RS.Controls.Timeline.Services;
using RS.Controls.Timeline.Utils;
#endregion


namespace RS.Controls.Timeline
{
    /// <summary>
    /// This class represents an argument for a TimeLine event.
    /// </summary>
    public class TimeLineEventArgs : EventArgs
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="TimeLineEventArgs"/> class.
        /// </summary>
        /// <param name="slice">The slice.</param>
        public TimeLineEventArgs(ITimeSlice slice)
        {
            _slice = slice;
        }

        private ITimeSlice _slice;
        public ITimeSlice Slice
        {
            get{ return _slice; }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public partial class TimeLine : UserControl
    {
        #region Fields
        //
        // Constants
        //
        private const int TIMER_RESOLUTION = 5;

        /// <summary>
        /// This list contains all time slices.
        /// </summary>
        private List<ITimeSlice> _timeSlices = new List<ITimeSlice>();

        /// <summary>
        /// Timer for the time line. 
        /// </summary>
        private HighPerfTimer _timer;

        //
        // Delegates and Events
        //
        public delegate void TimeSliceEventHandler(object sender, TimeLineEventArgs args);

        /// <summary>
        /// Occurs when the user clicks on a time slice.
        /// </summary>
        public event TimeSliceEventHandler OnTimeSliceClicked;

        /// <summary>
        /// Occurs when the end of the time line is reached.
        /// </summary>
        public event TimeSliceEventHandler OnEndReached;

        /// <summary>
        /// Occurs when the action method of a time slice is executed.
        /// </summary>
        public event TimeSliceEventHandler OnAction;

        private int _offsetLeft = 10;
        private int _offsetRight = 10;
        private int _scrubHeight = 10;
        private int _secSpacerWidth = 2;
        private int _tickedDistance = 8;
        private Color _bgColor = Color.LightGray;
        private Color _playHeadColor = Color.Red;
        private Color _tickedColor = Color.Brown;


        private int _numberOfTimeSlices = 40;
        private int _timerPeriod = 25;
        private int _duration = 10000;
        private bool _loop = false;
        private bool _showTicker = false;

        //
        // Internal used fields
        //
        private int _playHeadCurrPosX = 0;
        private bool _isPlayHeadClicked = false;
        private bool _isMousePressed = false;
        private Point _mouseClickedPt; 
        private Point _mouseReleasePt; 
        #endregion //Fields

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="TimeLine"/> class.
        /// </summary>
        public TimeLine()
        {
            InitializeComponent();

            _playHeadCurrPosX = _offsetLeft;
            DoubleBuffered = true;
            Margin = new Padding(0);
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets a value indicating whether this instance is loop.
        /// </summary>
        /// <value><c>true</c> if this instance is loop; otherwise, <c>false</c>.</value>
        public bool Loop
        {
            get { return _loop; }
            set { _loop = value; }
        }

        /// <summary>
        /// Gets or sets the color of the background
        /// </summary>
        /// <value>The color of the bg.</value>
        public Color BackgroundColor
        {
            get
            {
                return _bgColor;
            }

            set
            {
                _bgColor = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets the color of the ticked.
        /// </summary>
        /// <value>The color of the ticked.</value>
        public Color TickerColor
        {
            get { return _tickedColor; }
            set
            {
                _tickedColor = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets the color of the play head.
        /// </summary>
        /// <value>The color of the play head.</value>
        public Color PlayHeadColor
        {
            get { return _playHeadColor; }
            set
            {
                _playHeadColor = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets the period of the timer.
        /// </summary>
        /// <value>The timer period.</value>
        public int TimerPeriod
        {
            get { return _timerPeriod; }
            set { _timerPeriod = value; }
        }

        /// <summary>
        /// Gets or sets the ticked distance.
        /// </summary>
        /// <value>The ticked distance.</value>
        public int TickedDistance
        {
            get
            {
                return _tickedDistance;
            }

            set
            {
                _tickedDistance = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [show ticker].
        /// </summary>
        /// <value><c>true</c> if [show ticker]; otherwise, <c>false</c>.</value>
        public bool ShowTicker
        {
            get { return _showTicker; }
            set
            {
                _showTicker = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets the number of time slices.
        /// </summary>
        /// <value>The number of time slices.</value>
        public int CountOfTimeSlices
        {
            get { return _numberOfTimeSlices; }
            set
            {
                _numberOfTimeSlices = value;

                // Create all time slices
                CreateTimeSlices();

                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets the duration of one time line cycle. Duration is in ms.
        /// </summary>
        /// <value>The duration in ms.</value>
        public int Duration
        {
            get { return _duration; }
            set { _duration = value; }
        }

        /// <summary>
        /// Gets or sets the width of the sec spacer.
        /// </summary>
        /// <value>The width of the sec spacer.</value>
        public int SecSpacerWidth
        {
            get
            {
                return _secSpacerWidth;
            }
            set
            {
                _secSpacerWidth = value;
                Invalidate();
            }
        }
        #endregion //Properties

        #region Methods
        /// <summary>
        /// Handles the Load event of the TimeLine control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void TimeLine_Load(object sender, System.EventArgs e)
        {
            CreateTimeSlices();

            _timeSlices[0].IsActive = true;
            Invalidate();
        }

        /// <summary>
        /// This method add time slices.
        /// </summary>
        private void CreateTimeSlices()
        {
            // Clear al time sclices
            _timeSlices.Clear();

            // Add _numberOfTimeSclices of ITimeScliceObjects
            int xStep = (Width / _numberOfTimeSlices) - _secSpacerWidth;
            int xCurr = _offsetLeft;
            for (int i = 1; i < _numberOfTimeSlices + 1; i++)
            {
                TimeSlice ts = new TimeSlice(xCurr, _scrubHeight, xStep, Height - _scrubHeight);

                if (i % _tickedDistance == 0)
                {
                    ts.IsTicked = true;
                }
                else
                    ts.IsTicked = false;

                xCurr += (xStep + _secSpacerWidth);
                _timeSlices.Add(ts);
            }
        }

        /// <summary>
        /// Löst das <see cref="E:System.Windows.Forms.Control.Paint"/>-Ereignis aus.
        /// </summary>
        /// <param name="e">Eine Instanz von <see cref="T:System.Windows.Forms.PaintEventArgs"/>, die die Ereignisdaten enthält.</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            base.OnPaint(e);

            g.SmoothingMode = SmoothingMode.AntiAlias;

            // Draw the background
            Rectangle rect = new Rectangle(0, 0, Width, Height);
            SolidBrush brush = new SolidBrush(_bgColor);
            g.FillRectangle(brush, rect);
    
            // Draw all time slices
            DrawTimeSlices(g);

            // Draw PlayHead
            DrawPlayHead(g);
        }

        /// <summary>
        /// Draws the time slices.
        /// </summary>
        /// <param name="g">The g.</param>
        private void DrawTimeSlices(Graphics g)
        {
            int xStep = (Width / _numberOfTimeSlices) - _secSpacerWidth;
            int xCurr = _offsetLeft;
            ITimeSlice slice;
            for (int i = 0; i < _numberOfTimeSlices; i++)
            {
                slice = _timeSlices[i];

                // Set new dimensions
                slice.X = xCurr;
                slice.Y = _scrubHeight;
                slice.W = xStep;
                slice.H = Height - _scrubHeight;

                if ((i+1) % _tickedDistance == 0)
                {
                    slice.IsTicked = true;
                }
                else
                {
                    slice.IsTicked = false;
                }

                // Check for collision with playhead
                if (slice.IsCollision(_playHeadCurrPosX))
                {
                    // Execute the action method here
                    slice.Action();

                    // Last time slice reached
                    if (i == _numberOfTimeSlices - 1)
                    {
                        if (OnEndReached != null)
                        {
                            OnEndReached(this, new TimeLineEventArgs(slice));
                        }
                    }
                }
                slice.Draw(g);

                xCurr += (xStep + _secSpacerWidth);
            }
        }

        /// <summary>
        /// Draws the play head.
        /// </summary>
        private void DrawPlayHead(Graphics g)
        {
            Rectangle rect = new Rectangle(_playHeadCurrPosX, 0, _secSpacerWidth, Height);
            SolidBrush brush = new SolidBrush(Color.Red);
            g.FillRectangle(brush, rect);

            Point[] polygon = new Point[7];
            polygon[0] = new Point(_playHeadCurrPosX - 5, 0);
            polygon[1] = new Point(_playHeadCurrPosX + 5, 0);
            polygon[2] = new Point(_playHeadCurrPosX + 5, 5);
            polygon[3] = new Point(_playHeadCurrPosX + 0, 10);
            polygon[4] = new Point(_playHeadCurrPosX - 0, 10);
            polygon[5] = new Point(_playHeadCurrPosX - 5, 5);
            polygon[6] = new Point(_playHeadCurrPosX - 5, 0);
            g.FillPolygon(brush, polygon);
        }

        #region Timeline control
        /// <summary>
        /// Plays this instance.
        /// </summary>
        public void Play()
        {
            if (_timer != null)
            {
                _timer.Dispose();
                _timer = null;
            }

            // Create new timer
            _timer = new HighPerfTimer();
            _timer.Resolution = TIMER_RESOLUTION;
            _timer.Period = _timerPeriod;
            _timer.Tick += new System.EventHandler(OnTimerTickEvent);

            _timer.Start();
        }

        /// <summary>
        /// Called when [timer tick event].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void OnTimerTickEvent(object sender, System.EventArgs e)
        {
            int width = Width - _offsetLeft;
            //int durationTmp = 
            float step = Width / (_duration / _timerPeriod);

            _playHeadCurrPosX += (int)step;
            Invalidate();

            // Check for end of time line
            if (_playHeadCurrPosX > Width)
            {
                if (_loop)
                {
                    Reset();
                    Play();
                }
                else
                {
                    // TODO: Throw end of time slice reached event
                    Reset();
                }
            }

        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public void Stop()
        {
            if (_timer == null)
            {
                return;
            }

            _timer.Stop();
        }

        /// <summary>
        /// Pauses this instance.
        /// </summary>
        public void Pause()
        {
            Stop();
        }

        /// <summary>
        /// Resets this instance.
        /// </summary>
        public void Reset()
        {
            Stop();
            _playHeadCurrPosX = _offsetLeft;
            Invalidate();
        }
        #endregion

        /// <summary>
        /// Handles the MouseDown event of the TimeLine control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        private void TimeLine_MouseDown(object sender, MouseEventArgs e)
        {
            Point mp = e.Location;

            // Creating bounding box for mouse click
            Point[] bBox = new Point[4];
            bBox[0] = new Point(_playHeadCurrPosX - 5, 0);
            bBox[1] = new Point(_playHeadCurrPosX + 5, 0);
            bBox[2] = new Point(_playHeadCurrPosX + 5, _scrubHeight);
            bBox[3] = new Point(_playHeadCurrPosX - 5, _scrubHeight);

            // Check if box contains mouse click for play head
            GraphicsPath obj = new GraphicsPath();
            obj.AddPolygon(bBox);
            if (obj.IsVisible(mp))
            {
                _isPlayHeadClicked = true;
            }

            // Set active time slice
            if (!_isPlayHeadClicked)
            {
                // Test for collision with time slice
                foreach (TimeSlice slice in _timeSlices)
                {
                    if (slice.IsCollision(mp.X))
                    {
                        slice.IsActive = true;
                    }
                    else
                    {
                        slice.IsActive = false;
                    }
                }
            }

            _isMousePressed = true;
            _mouseClickedPt = mp;

            Invalidate();
        }

        /// <summary>
        /// Handles the MouseUp event of the TimeLine control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        private void TimeLine_MouseUp(object sender, MouseEventArgs e)
        {
            _mouseReleasePt = e.Location;
            int count = e.Clicks;

            _isPlayHeadClicked = false;
            _isMousePressed = false;

            // Snap playhead to closest time slice
            foreach (TimeSlice slice in _timeSlices)
            {
                // Double click
                if (slice.IsActive && count >= 2)
                {
                    // TODO: Throw double click event
                    LoggingService.Debug("Double click");
                }
                else if (slice.IsActive && count == 1) // Single click
                {
                    // TODO: Throw single click event
                    LoggingService.Debug("Single click");

                    if (OnTimeSliceClicked != null)
                    {
                        OnTimeSliceClicked(this, new TimeLineEventArgs(slice));
                    }
                }

                // Move the playhead to the beginning of a time slice
                if (slice.IsCollision(_playHeadCurrPosX))
                {
                    _playHeadCurrPosX = slice.X - 1;
                }
            }

            Invalidate();
        }
        
        /// <summary>
        /// Handles the MouseMove event of the TimeLine control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        private void TimeLine_MouseMove(object sender, MouseEventArgs e)
        {
            // If playhead is clicked
            if (_isPlayHeadClicked)
            {
                _playHeadCurrPosX = e.Location.X;

                // Bounds check for playhead
                if (_playHeadCurrPosX < _offsetLeft)
                {
                    _playHeadCurrPosX = _offsetLeft;
                }
                else if (_playHeadCurrPosX > (Width - 5))
                {
                    _playHeadCurrPosX = Width - 5;
                }
            }
            else // Make multiple selection of time slices on mouse move
            {
                if (_isMousePressed)
                {
                    int x = e.Location.X;

                    foreach (TimeSlice slice in _timeSlices)
                    {
                        if (slice.IsCollision(_mouseClickedPt.X, x))
                        {
                            slice.IsActive = true;
                        }
                    }    
                }
            }

            Invalidate();
        }

        /// <summary>
        /// </summary>
        /// <param name="keyData">Einer der <see cref="T:System.Windows.Forms.Keys"/>-Werte, der die zu verarbeitende Taste darstellt.</param>
        /// <returns>
        /// true, wenn die Taste vom Steuerelement verarbeitet wurde, andernfalls false.
        /// </returns>
        protected override bool ProcessDialogKey(Keys keyData)
        {
            return base.ProcessDialogKey(keyData);
        }
        #endregion //Methods
    }
}
